#include<sys/io.h>
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#define SOCKET int
#define INVALID_SOCKET  (SOCKET)(~0)
#define SOCKET_ERROR            (-1)
//#include<winsock2.h>
//#include<windows.h>

#include <errno.h>
#include<iostream>
#include<map>
//#include<ws2tcpip.h>
//#include<direct.h>
#include <netdb.h>
#include <algorithm>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>


//#pragma comment(lib,"ws2_32.lib") //Winsock Library
#define IDLE_INTERVAL 60000
#include "nlohmann/json.hpp"
using json = nlohmann::json;


#include "cmdline.h"
#include "CameraReader.h"
#include "ObjectDetectThread.h"
#include "commonFunction.h"

using namespace std;



struct CameraConfig {
	std::string channel;
	std::string rtsp_address;
	int width = 0;
	int height = 0;
	bool needCalibrate = false;
	bool needCrop = false;
	bool needResize = false;
	bool netCamera = true;
	bool needDetectObject = false;
	bool needDetectSecurity = false;
	int resizeWidth = 0;
	int resizeHeight = 0;
	int cropStartx = 0;
	int cropStarty = 0;
	int cropWidth = 0;
	int cropHeight = 0;
	int frameFrequency = 25;
	std::string maskFile;
	int useCount = 0;
	CameraReader* reader;
};

std::string replaceAll(std::string str, const std::string& from, const std::string& to) {
	size_t start_pos = 0;
	while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
		str.replace(start_pos, from.length(), to);
		start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
	}
	return str;
}

CameraConfig* generateCameraConfig(ShareQueue<std::vector<Detection>>* objectDetectionQueue, 
	ShareQueue<FrameInfo>* frameQueue,
	json configJson, int memorySize = 5)
{
	CameraConfig* config = new CameraConfig;
	config->channel = configJson["channel"];
	if (configJson["rtsp_address"].is_string()) {
		config->rtsp_address = configJson["rtsp_address"];
	}
	else {
		printf("The RTSP address have to be a string!");
	}
	if (configJson["maskFile"].is_string()) {
		config->maskFile = configJson["maskFile"];
		config->maskFile = replaceAll(config->maskFile, "/", "\\");
	}
	else {
		printf("The mask file have to be a string!");
	}
	if (configJson["width"].is_number_integer()) {
		config->width = configJson["width"];
	}
	else {
		printf("The width have to be positive integer!");
	}
	if (configJson["height"].is_number_integer()) {
		config->height = configJson["height"];
	}
	else {
		printf("The height have to be positive integer!");
	}
	if (configJson["needCalibrate"].is_boolean()) {
		config->needCalibrate = configJson["needCalibrate"];
	}
	if (configJson["needCrop"].is_boolean()) {
		config->needCrop = configJson["needCrop"];
		if (configJson["cropStartx"].is_number_integer()) {
			config->cropStartx = configJson["cropStartx"];
		}
		else {
			printf("The crop start X coordination have to be positive integer!");
		}
		if (configJson["cropStarty"].is_number_integer()) {
			config->cropStarty = configJson["cropStarty"];
		}
		else {
			printf("The crop start y coordination have to be positive integer!");
		}
		if (configJson["cropWidth"].is_number_integer()) {
			config->cropWidth = configJson["cropWidth"];
		}
		else {
			printf("The crop width have to be positive integer!");
		}
		if (configJson["cropHeight"].is_number_integer()) {
			config->cropHeight = configJson["cropHeight"];
		}
		else {
			printf("The crop height have to be positive integer!");
		}
	}
	if (configJson["needResize"].is_boolean()) {
		config->needResize = configJson["needResize"];
		if (configJson["resizeWidth"].is_number_integer()) {
			config->resizeWidth = configJson["resizeWidth"];
		}
		else {
			printf("The resize width have to be positive integer!");
		}
		if (configJson["resizeHeight"].is_number_integer()) {
			config->resizeHeight = configJson["resizeHeight"];
		}
		else {
			printf("The resize height have to be positive integer!");
		}
	}
	if (configJson["netCamera"].is_boolean()) {
		config->netCamera = configJson["netCamera"];
	}
	if (configJson["needDetectObject"].is_boolean()) {
		config->needDetectObject = configJson["needDetectObject"];
	}
	if (configJson["needDetectSecurity"].is_boolean()) {
		config->needDetectSecurity = configJson["needDetectSecurity"];
	}
	if (configJson["frameFrequency"].is_number_integer()) {
		config->frameFrequency = configJson["frameFrequency"];
	}
	/*CameraReader(LPCWSTR channel, string url, string mask_file_name, bool calibrate = false,
		bool shouldResize = false, vector<int> resizeDimension = { 0,0 },
		bool shouldCrop = false, vector<int> cropRectangle = { 0,0,0,0 },
		int memorySize = 5)*/
	vector<int> resizeDimension = { config->resizeWidth,config->resizeHeight };
	vector<int> cropRectangle = { config->cropStartx,config->cropStarty,config->cropWidth,config->cropHeight };
//	config->reader = new CameraReader(config->channel, config->rtsp_address, config->maskFile, config->needCalibrate, config->needResize,
//		resizeDimension, config->needCrop, cropRectangle, memorySize,config->netCamera);
//	config->reader->setFrameQueue(frameQueue);
//	config->reader->setObjectDetectionQueue(objectDetectionQueue);
	//todo
	//create thread;
//	config->reader->switchObjectDetection(config->needDetectObject);
//	config->reader->setExtractFrequency(config->frameFrequency);
	return config;
}

std::map<std::string, struct CameraConfig*> dealDisconnection(std::map<std::string, struct CameraConfig*> cameraConfigMap, json configJson, 
	ShareMap<std::string, ShareQueue<std::vector<Detection>>*> *objectResultsQueueMap, ShareMap<std::string, ShareQueue<FrameInfo>*> *frameQueueMap) {
	for (auto& element : configJson) {
		if (element["channel"].is_string()) {
			std::string channel = element["channel"];
			auto iterator = cameraConfigMap.find(channel);
			if (iterator != cameraConfigMap.end()) {
				cameraConfigMap[channel]->useCount--;
				if (cameraConfigMap[channel]->useCount <= 0) {
					cameraConfigMap[channel]->reader->stop();
					delete cameraConfigMap[channel]->reader;
					delete iterator->second;
					cameraConfigMap.erase(channel);
					objectResultsQueueMap->lock();
					objectResultsQueueMap->erase(channel);
					objectResultsQueueMap->unLock();
					frameQueueMap->lock();
					frameQueueMap->erase(channel);
					frameQueueMap->unLock();
				}
			}
		}
	}
	return cameraConfigMap;
}

bool dealReceivedMessage(char* readBuffer, string& currentJSONStr,string& result) {
	currentJSONStr += readBuffer;
	string echoString = "<__ECHO__>";
	bool needEcho = false;
	std::size_t ind = currentJSONStr.find(echoString);
	while (ind != std::string::npos) {
		needEcho = true;
		currentJSONStr.erase(ind, echoString.length());
		ind = currentJSONStr.find(echoString);
	}
	int index = currentJSONStr.find("<__JSONEND__>");
	if (index > 0) {
		result = currentJSONStr.substr(0, index);
		currentJSONStr = currentJSONStr.substr(index + 13, currentJSONStr.length());
		return true;
	}
	return needEcho;
}

int main(int argc, char* argv[])
{
	std::string current_path = currentPath();
	cmdline::parser a;
	a.add<int>("clients", 'c', "max clients", false, 30, cmdline::range(1, 100));
	a.add<int>("buffer", 'b', "max receive buffer of socket", false, 8192, cmdline::range(1, 102400));
	a.add<int>("port", 'p', "port number", false, 8888, cmdline::range(1, 65535));
	a.add<int>("mSize", 's', "share memory block buffer size", false, 5, cmdline::range(1, 10));
	a.add<std::string>("ODModel",'\0',"Object Detector Model",false,"objectDetect.pt");
	a.add<std::string>("ODCFile", '\0', "Object Detector class files", false, "objDetect.txt");
	a.add<int>("ODBatchSize", '\0', "Batch size for object detector", false, 5);
	a.add<int>("ODWidth", '\0', "Object Detector image width", false, 1280);
	a.add<int>("ODHeight", '\0', "Object Detector image height", false, 1280);
	a.add<float>("ODConf", '\0', "The confidence ratio for object detector", false, 0.25, cmdline::range(0.0, 0.99));
	a.add<float>("ODIOU", '\0', "The IOU confidence ratio for object detector", false, 0.45, cmdline::range(0.0, 0.99));
	a.add("ODGPU", '\0', "Using GPU for Object Detector");
	a.add<int>("ODDevice", '\0', "The device for Object Detector", false, 0, cmdline::range(1, 100));
	a.parse_check(argc, argv);

	ShareMap<std::string, ShareQueue<std::vector<Detection>>*>* objectResultsQueueMap;
	ShareMap<std::string, ShareQueue<FrameInfo>*>* frameQueueMap;

	int maxClients = a.get<int>("clients");
	int maxReceive = a.get<int>("buffer");
	int port = a.get<int>("port");
	int memorySize = a.get<int>("mSize");
	std::string objectDetectModel = a.get<std::string>("ODModel");
	std::string objectDetectFile = a.get<std::string>("ODCFile");
	std::string objectModelPath = joinPath(current_path, "models", objectDetectModel);
	std::string objectClassesPath = joinPath(current_path, "models", objectDetectFile);
	int objectDetectBatchSize = a.get<int>("ODBatchSize");
	int objectDetectWidth = a.get<int>("ODWidth");
	int objectDetectHeight = a.get<int>("ODHeight");
	float objectDetectConf = a.get<float>("ODConf");
	float objectDetectIOU = a.get<float>("ODIOU");
	//Todo fix it;
	bool objectDetectUseGPU = !a.exist("ODGPU");
	int objectDetectDevice = a.get<int>("ODDevice");
	DetectThread* objectDetectThread = new DetectThread(objectClassesPath, objectModelPath,
		 objectDetectBatchSize, objectDetectWidth, objectDetectHeight, objectDetectConf, objectDetectIOU, objectDetectUseGPU, objectDetectDevice);
	if (objectDetectThread->isAvaliable()) {
		objectDetectThread->initilaize();
		if (objectDetectThread->isAvaliable()) {
			objectResultsQueueMap = new ShareMap<std::string, ShareQueue<std::vector<Detection>>*>();
			frameQueueMap = new ShareMap<std::string, ShareQueue<FrameInfo>*>();
			objectDetectThread->setReceiveQueueMap(frameQueueMap);
			objectDetectThread->setSendQueueMap(objectResultsQueueMap);
			objectDetectThread->start();
		}
		else {
			printf("The object detector is not available, system exited unexpectly!");
			exit(-1);
		}
	}
	else {
		printf("The object detector is not available, system exited unexpectly!");
		exit(-1);
	}
	
	/*DetectThread(std::string classesFile, std::string modelPath,
		int batchSize = 1, int imgWidth = 640, int imgHeight = 640,
		float confThreshold = (double)0.25, float iouThreshold = (double)0.45, bool useGPU = false, int gpuId = 0, long max_gpu_memory = 2147483648)*/

//	WSADATA wsa;
	SOCKET master, new_socket,  s;
	SOCKET *client_sockets;
	client_sockets = new SOCKET[maxClients];
	std::chrono::high_resolution_clock::time_point* clientLastActiveTimeStamp;
	clientLastActiveTimeStamp = new std::chrono::high_resolution_clock::time_point[maxClients];
	std::map<std::string, struct CameraConfig*> cameraConfigMap;
	struct sockaddr_in server, address;
	int activity, addrlen, i, valread;
	const char* message = "Image Processing Daemon v1.0 \r\n";

	//size of our receive buffer, this is string length.
	
	//set of socket descriptors
	fd_set readfds;
	//1 extra for null character, string termination
	char** readBuffer = new char*[maxClients];
	std::string* receivedJSONStrs = new string[maxClients];
	for (int i = 0; i < maxClients; i++) {
		//readBuffer[i] = (char*)malloc((maxReceive + 1) * sizeof(char));
		readBuffer[i] = new char[maxReceive + 1];
		receivedJSONStrs[i] = "";
	}

	for (i = 0; i < maxClients; i++)
	{
		client_sockets[i] = 0;
	}

	printf("\nInitialising Winsock...");
//	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
//	{
//		printf("Failed. Error Code : %d", WSAGetLastError());
//		exit(EXIT_FAILURE);
//	}
//
//	printf("Initialised.\n");

	//Create a socket
	if ((master = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
		printf("Could not create socket : %s", strerror(errno));
		exit(EXIT_FAILURE);
	}

	printf("Socket created.\n");

	//Prepare the sockaddr_in structure
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons(port);

	if(::bind(master, (sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
	{
		printf("Bind failed with error code : %s",strerror(errno));
		exit(EXIT_FAILURE);
	}

	puts("Bind done");

	//Listen to incoming connections
	listen(master, 3);

	//Accept and incoming connection
	puts("Waiting for incoming connections...");

	addrlen = sizeof(struct sockaddr_in);

	//store the json content for any client;
	std::vector<json>* socketJsons = new std::vector<json>[maxClients];


	while (TRUE)
	{
		//clear the socket fd set
		FD_ZERO(&readfds);

		//add master socket to fd set
		FD_SET(master, &readfds);

		//add child sockets to fd set
		for (i = 0; i < maxClients; i++)
		{
			s = client_sockets[i];
			if (s > 0)
			{
				FD_SET(s, &readfds);
			}
		}

		//wait for an activity on any of the sockets, timeout is NULL , so wait indefinitely
		activity = select(0, &readfds, NULL, NULL, NULL);

		if (activity == SOCKET_ERROR)
		{
			printf("select call failed with error code : %s",strerror(errno));
			exit(EXIT_FAILURE);
		}

		//If something happened on the master socket , then its an incoming connection
		if (FD_ISSET(master, &readfds))
		{
			if ((new_socket = accept(master, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0)
			{
				perror("accept");
				exit(EXIT_FAILURE);
			}

			//inform user of socket number - used in send and receive commands
			char IPdotdec[20];
			inet_ntop(AF_INET, (const void*)&address.sin_addr, IPdotdec, 16);
			printf("New connection , socket fd is %d , ip is : %s , port : %d \n", new_socket, IPdotdec,ntohs(address.sin_port));

			//send new connection greeting message
			if (send(new_socket, message, strlen(message), 0) != strlen(message))
			{
				perror("send failed");
			}

			puts("Welcome message sent successfully");

			//add new socket to array of sockets
			for (i = 0; i < maxClients; i++)
			{
				if (client_sockets[i] == 0)
				{
					client_sockets[i] = new_socket;
					clientLastActiveTimeStamp[i] = std::chrono::high_resolution_clock::now();
					printf("Adding to list of sockets at index %d \n", i);
					break;
				}
			}
		}

		//else its some IO operation on some other socket :)
		for (i = 0; i < maxClients; i++)
		{
			s = client_sockets[i];
			//if client presend in read sockets             
			if (FD_ISSET(s, &readfds))
			{
				//get details of the client
				getpeername(s, (struct sockaddr*)&address, (socklen_t*)&addrlen);

				//Check if it was for closing , and also read the incoming message
				//recv does not place a null terminator at the end of the string (whilst printf %s assumes there is one).
				valread = recv(s, readBuffer[i], maxReceive, 0);

				if (valread == SOCKET_ERROR)
				{
					int error_code = errno,strerror(errno);
					if (error_code == ECONNRESET)
					{
						//Somebody disconnected , get his details and print
						char IPdotdec[20];
						inet_ntop(AF_INET, (const void*)&address.sin_addr, IPdotdec, 16);
						printf("Host disconnected unexpectedly , ip %s , port %d \n", IPdotdec, ntohs(address.sin_port));
						//Close the socket and mark as 0 in list for reuse
						close(s);
						client_sockets[i] = 0;
					}
					else
					{
						printf("recv failed with error code : %d", error_code);
					}
				}
				if (valread == 0)//shoule be modified as valread == 0
				{
					//Somebody disconnected , get his details and print
					auto now = std::chrono::high_resolution_clock::now();
					auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - clientLastActiveTimeStamp[i]);
					if (diff.count() > IDLE_INTERVAL) {
						char IPdotdec[20];
						inet_ntop(AF_INET, (const void*)&address.sin_addr, IPdotdec, 16);
						printf("Host disconnected , ip %s , port %d \n", IPdotdec, ntohs(address.sin_port));
						for (int j = 0; j < socketJsons[i].size(); j++) {
							cameraConfigMap = dealDisconnection(cameraConfigMap, socketJsons[i][j], objectResultsQueueMap, frameQueueMap);
						}
						//Close the socket and mark as 0 in list for reuse
						close(s);
						client_sockets[i] = 0;
					}
				}
				//Echo back the message that came in
				else
				{
					//add null character, if you want to use with printf/puts or other string handling functions
					clientLastActiveTimeStamp[i] = std::chrono::high_resolution_clock::now();
					readBuffer[i][valread] = '\0';		
					string jsonStr = "";
					bool needEcho = dealReceivedMessage(readBuffer[i], receivedJSONStrs[i],jsonStr);
					if (jsonStr != "") {
						//cameraConfigMap = dealDisconnection(cameraConfigMap, socketJsons[i]);
						json configJson = json::parse(jsonStr, nullptr, false);
						if (configJson.is_discarded()) {
							printf("Parse json parameters error! Please check!");
							char IPdotdec[20];
							inet_ntop(AF_INET, (const void*)&address.sin_addr, IPdotdec, 16);

							printf("%s:%d - %s \n", IPdotdec, ntohs(address.sin_port), readBuffer[i]);
						}
						else {
							if (configJson.is_array()) {
								socketJsons[i].emplace_back(configJson);
								for (auto& element : configJson) {
									if (element["channel"].is_string()) {
										std::string channel = element["channel"];
										bool power = element["power"];
										if (power) {
											auto iterator = cameraConfigMap.find(channel);
											if (iterator != cameraConfigMap.end()) {
												cameraConfigMap[channel]->useCount++;
											}
											else {
												ShareQueue<std::vector<Detection>>* objectDetectionQueue = new ShareQueue<std::vector<Detection>>(5);
												ShareQueue<FrameInfo>* frameQueue = new ShareQueue<FrameInfo>(5);
												objectResultsQueueMap->put(channel, objectDetectionQueue);
												frameQueueMap->put(channel, frameQueue);
												//objectDetectThread->setReceiveQueueMap(frameQueueMap);
												//objectDetectThread->setSendQueueMap(objectResultsQueueMap);
												cameraConfigMap[channel] = generateCameraConfig(objectDetectionQueue, frameQueue,
													element, memorySize);
												cameraConfigMap[channel]->useCount++;
//												cameraConfigMap[channel]->reader->initialize();
												cameraConfigMap[channel]->reader->start();
											}
										}
										else {
											cameraConfigMap = dealDisconnection(cameraConfigMap, configJson, objectResultsQueueMap, frameQueueMap);
										}
									}
								}
							}
							else {
								printf("The json parameters have to be an array,including multiple configurations");
								char IPdotdec[20];
								inet_ntop(AF_INET, (const void*)&address.sin_addr, IPdotdec, 16);
								printf("%s:%d - %s \n", IPdotdec, ntohs(address.sin_port), readBuffer[i]);
							}
						}
					}
					if (needEcho) {
						const char* echoString = "<__ECHO__>";
						if (send(s, echoString, strlen(echoString), 0) != strlen(echoString))
						{
							perror("send heart echo failed");
						}
					}
					//send(s, buffer, valread, 0);
				}
			}
		}
	}	

	/*while (cameraConfigMap.size() > 0) {
		auto iterator = cameraConfigMap.end();
		CameraConfig* temp;
		temp = iterator->second;
		iterator->second = NULL;
		cameraConfigMap.erase(iterator);
		delete temp;
	}

	for (int i = 0; i < maxClients; i++) {
		delete readBuffer[i];
		if (client_sockets[i]) {
			closesocket(client_sockets[i]);
		}
	}*/
	delete readBuffer;
	delete client_sockets;
//	WSACleanup();
	return 0;
}




// Run program: Ctrl + F5 or Debug > Start Without Debugging menu
// Debug program: F5 or Debug > Start Debugging menu

// Tips for Getting Started: 
//   1. Use the Solution Explorer window to add/manage files
//   2. Use the Team Explorer window to connect to source control
//   3. Use the Output window to see build output and other messages
//   4. Use the Error List window to view errors
//   5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project
//   6. In the future, to open this project again, go to File > Open > Project and select the .sln file
